package org.zffc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

/**
 * @Description:
 * @Note: windows下启动vault
 * @Author: zhangfei
 * @Date: 2025/4/2 10:43
 **/
public class ZffcVaultApplication {
    private static final Logger logger = LoggerFactory.getLogger(ZffcVaultApplication.class);

    private static final Integer port = 8200;

    private static final String mode = "test";

    public static void main(String[] args) {
        try {
            // 加载配置
            Map<String, Object> config = loadYamlConfig("application.yml");
            List<Map<String, Object>> apps = getAppsList(config);
            List<String> unsealTokens = (List<String>) config.get("unsealed-token");

            // 启动Vault
            for (Map<String, Object> appConfig : apps) {
                if (mode.equals(appConfig.get("mode").toString())) {

                    Boolean flag = isPortAvailable(port);
                    if (flag) {
                        launchVaultSilently(appConfig, unsealTokens);
                    } else {
                        exit(appConfig);
                    }
                }
            }

        } catch (Exception e) {
            System.err.println("Vault启动失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static void launchVaultSilently(Map<String, Object> appConfig, List<String> unsealTokens) throws Exception {
        String cmd = (String) appConfig.get("cmd");
        String args = (String) appConfig.getOrDefault("args", "");
        String workingDir = (String) appConfig.getOrDefault("workingDir", "");
        String logFile = (String) appConfig.getOrDefault("logFile", "vault.log");

        // 构建Vault专用启动命令
        String fullCommand = buildVaultCommand(cmd, args);

        ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", fullCommand);

        // 设置工作目录
        if (!workingDir.isEmpty()) {
            pb.directory(new File(workingDir));
        }

        // 重定向输出
        pb.redirectErrorStream(true);
        // 覆盖而非追加
        pb.redirectOutput(ProcessBuilder.Redirect.to(new File(logFile)));

        // 启动
        Process process = pb.start();

        Thread.sleep(500);

        String mode = appConfig.get("mode").toString();
        if ("dev".equals(mode)) {
            String unsealKey = null, rootToken = null;

            try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.contains("Unseal Key")) {
                        unsealKey = line;
                    }
                    if (line.contains("Root Token")) {
                        rootToken = line;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }


            logger.info("Vault已启动，日志输出到: " + new File(logFile).getAbsolutePath());
            logger.info("\n----------------------------------------------------------\n\t" +
                    "Application Zffc-Boot is running! Access URLs:\n\t" +
                    "Local: \t\thttp://localhost:8200" + "\n\t" +
                    unsealKey + "\n\t" +
                    rootToken + "\n\t" +
                    "----------------------------------------------------------");
        } else if ("test".equals(mode)) {
            Boolean flag = ZffcVaultApplication.unsealVault(appConfig, unsealTokens);
            if (flag) {
                logger.info("Vault已启动，日志输出到: " + new File(logFile).getAbsolutePath());
                logger.info("\n----------------------------------------------------------\n\t" +
                        "Application Zffc-Boot is running! Access URLs:\n\t" +
                        "Local: \t\thttp://localhost:8200" + "\n\t" +
                        "----------------------------------------------------------");
            }
        }
    }

    public static boolean unsealVault(Map<String, Object> appConfig, List<String> unsealTokens) throws IOException, InterruptedException {
        logger.info("开始解封 Vault...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String workingDir = (String) appConfig.getOrDefault("workingDir", "");
        String cmd = (String) appConfig.get("cmd");
        Collections.shuffle(unsealTokens); // 随机打乱
        for(String unsealToken: unsealTokens.subList(0, 3)) {
            // 执行 vault unseal 命令
            try {
                ProcessBuilder processBuilder = new ProcessBuilder()
                        .command(cmd, "operator", "unseal", unsealToken)
                        .redirectErrorStream(true)
                        .directory(new File(workingDir));
                // 设置环境变量
                Map<String, String> env = processBuilder.environment();
                env.put("VAULT_ADDR", "http://127.0.0.1:8200");

                Process process = processBuilder.start();
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;


    }

    private static boolean checkUnsealStatus(String cmd, String workingDir) {
        try {
            Process process = new ProcessBuilder()
                    .command(cmd, "status")
                    .directory(new File(workingDir))
                    .redirectErrorStream(true)
                    .start();

            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));

            String line;
            while ((line = reader.readLine()) != null) {
                if (line.contains("Sealed") && line.contains("false")) {
                    return true;
                }
            }

            return process.waitFor() == 0 && line != null;

        } catch (IOException | InterruptedException e) {
            System.err.println("检查状态时出错: " + e.getMessage());
            return false;
        }
    }

    private static void exit(Map<String, Object> appConfig) throws Exception {
        String logFile = (String) appConfig.getOrDefault("logFile", "vault.log");
        // 查找使用指定端口的进程ID
        ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "netstat -ano | findstr \":" + port + "\"");
        pb.redirectErrorStream(true);
        Process process = pb.start();

        Set<String> pidSet = new HashSet<>();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 解析输出以获取PID
                String[] parts = line.trim().split("\\s+");
                if (parts.length >= 5 && parts[1].contains(":" + port)) {
                    String currentPid = parts[parts.length - 1];
                    logger.info("Found process using port " + port + " with PID: " + currentPid);
                    pidSet.add(currentPid);

                }
            }
        }
        // 去除0，去除等待状态
        pidSet.remove("0");
        if (pidSet.size() == 1) {
            // 终止进程
            ProcessBuilder killPb = new ProcessBuilder("cmd.exe", "/c", "taskkill /F /PID " + pidSet.stream().iterator().next());
            // 重定向输出
            pb.redirectErrorStream(true);
            pb.redirectOutput(ProcessBuilder.Redirect.appendTo(new File(logFile)));

            Process killProcess = killPb.start();
            killProcess.waitFor();
            logger.info("Valut已停止，日志输出到: " + new File(logFile).getAbsolutePath());
        } else {
            logger.error("停止Valut失败，pidSet：{}", pidSet);
        }
    }

    /**
     * 检查本地端口是否可用
     *
     * @param port 要检查的端口号
     * @return true表示端口可用，false表示已被占用
     */
    public static boolean isPortAvailable(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            serverSocket.setReuseAddress(true);
            return true;
        } catch (IOException e) {
            // 端口已被占用或其他错误
            return false;
        }
    }

    private static String buildVaultCommand(String cmd, String args) {
        // EMQX需要特殊处理，不使用start /B，而是依靠emqx.cmd自身的后台能力
        StringBuilder command = new StringBuilder();

        // 处理带空格的路径
        if (cmd.contains(" ")) {
            command.append("\"").append(cmd).append("\"");
        } else {
            command.append(cmd);
        }

        // 添加参数
        if (!args.isEmpty()) {
            command.append(" ").append(args);
        }

        return command.toString();
    }

    // 以下方法与之前示例相同
    private static Map<String, Object> loadYamlConfig(String filePath) throws Exception {
        Yaml yaml = new Yaml();
        try (InputStream in = Files.newInputStream(Paths.get(
                ZffcVaultApplication.class.getClassLoader().getResource(filePath).toURI()))) {
            return yaml.load(in);
        }
    }

    @SuppressWarnings("unchecked")
    private static List<Map<String, Object>> getAppsList(Map<String, Object> config) {
        Object appsObj = config.get("apps");
        try {
            if (appsObj instanceof List) {
                Map<String, Object> vault = (Map<String, Object>) ((List<?>) appsObj).get(0);
                return (List<Map<String, Object>>) vault.get("environment");
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid 'environment' configuration");
        }
        throw new IllegalArgumentException("Invalid 'apps' configuration");
    }
}
